<!doctype html>
<html>
  <head>
    <meta charset=utf-8>
    <title>RTCRtpScriptTransformer.generateKeyFrame tests</title>
    <meta name='timeout' content='long'>
    <script src='/resources/testharness.js'></script>
    <script src='/resources/testharnessreport.js'></script>
    <script src=/resources/testdriver.js></script>
    <script src=/resources/testdriver-vendor.js></script>
    <script src='../mediacapture-streams/permission-helper.js'></script>
  </head>
  <body>
    <video id='video1' autoplay></video>
    <video id='video2' autoplay></video>
    <script src ='routines.js'></script>
    <script src ='../webrtc/simulcast/simulcast.js'></script>
    <script src ='../webrtc/RTCPeerConnection-helper.js'></script>
    <script src='../webrtc/third_party/sdp/sdp.js'></script>
    <script>

const generateKeyFrame = (port, opts) => postMethod(port, 'generateKeyFrame', opts);
const waitForFrame = port => postMethod(port, 'waitForFrame');

promise_test(async (test) => {
  const {sender, receiver} = await createConnectionWithTransform(test, 'script-transform-generateKeyFrame.js', {audio: true});
  let message = await waitForFrame(sender.transform.port);
  assert_equals(message, 'got frame');

  // No rids
  message = await generateKeyFrame(sender.transform.port);
  assert_equals(message.result, 'failure');
  assert_equals(message.value, 'InvalidStateError', `Message: ${message.message}`);

  message = await waitForFrame(receiver.transform.port);
  assert_equals(message, 'got frame');

  // No rids
  message = await generateKeyFrame(receiver.transform.port);
  assert_equals(message.result, 'failure');
  assert_equals(message.value, 'InvalidStateError', `Message: ${message.message}`);
}, 'generateKeyFrame() throws for audio');

promise_test(async (test) => {
  const {sender, receiver} = await createConnectionWithTransform(test, 'script-transform-generateKeyFrame.js', {video: true});
  let message = await waitForFrame(sender.transform.port);
  assert_equals(message, 'got frame');

  // No rids
  message = await generateKeyFrame(sender.transform.port);
  assert_equals(message.result, 'success');
  // value should be a timestamp
  assert_equals(typeof message.value, 'number');
  assert_greater_than(message.value, 0);

  // No rids
  message = await generateKeyFrame(receiver.transform.port);
  assert_equals(message.result, 'failure');
  assert_equals(message.value, 'InvalidStateError', `Message: ${message.message}`);

  video1.srcObject = new MediaStream([receiver.track]);
  await video1.play();
}, 'generateKeyFrame(null) resolves for video sender, and throws for video receiver');

promise_test(async (test) => {
  const {sender, receiver} = await createConnectionWithTransform(test, 'script-transform-generateKeyFrame.js', {video: true});
  let message = await waitForFrame(sender.transform.port);
  assert_equals(message, 'got frame');

  // Invalid rid, empty string
  message = await generateKeyFrame(sender.transform.port, {rid: ''});
  assert_equals(message.result, 'failure');
  assert_equals(message.value, 'NotAllowedError', `Message: ${message.message}`);

  // Invalid rid, bad ASCII characters
  message = await generateKeyFrame(sender.transform.port, {rid: '!?'});
  assert_equals(message.result, 'failure');
  assert_equals(message.value, 'NotAllowedError', `Message: ${message.message}`);

  // Invalid rid, bad ASCII characters (according to RFC 8852, but not RFC 8851)
  message = await generateKeyFrame(sender.transform.port, {rid: 'foo-bar'});
  assert_equals(message.result, 'failure');
  assert_equals(message.value, 'NotAllowedError', `Message: ${message.message}`);

  // Invalid rid, bad ASCII characters (according to RFC 8852, but not RFC 8851)
  message = await generateKeyFrame(sender.transform.port, {rid: 'foo_bar'});
  assert_equals(message.result, 'failure');
  assert_equals(message.value, 'NotAllowedError', `Message: ${message.message}`);

  // Invalid rid, bad non-ASCII characters
  message = await generateKeyFrame(sender.transform.port, {rid: '(╯°□°)╯︵ ┻━┻'});
  assert_equals(message.result, 'failure');
  assert_equals(message.value, 'NotAllowedError', `Message: ${message.message}`);

  // Invalid rid, too long
  message = await generateKeyFrame(sender.transform.port, {rid: 'a'.repeat(256)});
  assert_equals(message.result, 'failure');
  assert_equals(message.value, 'NotAllowedError', `Message: ${message.message}`);
}, 'generateKeyFrame throws NotAllowedError for invalid rid');

promise_test(async (test) => {
  const {sender, receiver} = await createConnectionWithTransform(test, 'script-transform-generateKeyFrame.js', {video: true});
  let message = await waitForFrame(sender.transform.port);
  assert_equals(message, 'got frame');

  message = await generateKeyFrame(sender.transform.port, {rid: 'foo'});
  assert_equals(message.result, 'failure');
  assert_equals(message.value, 'NotFoundError', `Message: ${message.message}`);
}, 'generateKeyFrame throws NotFoundError for unknown rid');

promise_test(async (test) => {
  const {sender, receiver} = await createConnectionWithTransform(test, 'script-transform-generateKeyFrame.js', {video: true});
  let message = await waitForFrame(sender.transform.port);
  assert_equals(message, 'got frame');

  message = await generateKeyFrame(sender.transform.port);
  assert_equals(message.result, 'success');

  const senderTransform = sender.transform;
  sender.transform = null;

  message = await generateKeyFrame(senderTransform.port);
  assert_equals(message.result, 'failure');
  assert_equals(message.value, 'InvalidStateError', `Message: ${message.message}`);
}, 'generateKeyFrame throws for unset transforms');

promise_test(async (test) => {
  const {sender, receiver} = await createConnectionWithTransform(test, 'script-transform-generateKeyFrame.js', {video: true});
  let message = await waitForFrame(sender.transform.port);
  assert_equals(message, 'got frame');

  message = await generateKeyFrame(sender.transform.port);
  assert_equals(message.result, 'success');
  // value should be a timestamp
  assert_equals(typeof message.value, 'number');
  assert_greater_than(message.value, 0);
  const timestamp = message.value;

  message = await generateKeyFrame(sender.transform.port);
  assert_equals(message.result, 'success');
  // value should be a timestamp
  assert_equals(typeof message.value, 'number');
  assert_greater_than(message.value, timestamp);
}, 'generateKeyFrame timestamp should advance');

promise_test(async (test) => {
  const {sender, receiver} = await createConnectionWithTransform(test, 'script-transform-generateKeyFrame.js', {video: true});
  let message = await waitForFrame(sender.transform.port);
  assert_equals(message, 'got frame');

  message = await generateKeyFrame(sender.transform.port);
  assert_equals(message.result, 'success');
  const count = message.count;

  message = await generateKeyFrame(sender.transform.port);
  assert_equals(message.result, 'success');
  assert_greater_than(message.count, count);
}, 'await generateKeyFrame, await generateKeyFrame should see an increase in count of keyframes');

promise_test(async (test) => {
  const {sender, receiver, senderPc, receiverPc} = await createConnectionWithTransform(test, 'script-transform-generateKeyFrame.js', {video: true});
  let message = await waitForFrame(sender.transform.port);
  assert_equals(message, 'got frame');

  message = await generateKeyFrame(sender.transform.port);
  assert_equals(message.result, 'success');

  senderPc.getTransceivers()[0].direction = 'inactive';
  await senderPc.setLocalDescription();
  await receiverPc.setRemoteDescription(senderPc.localDescription);
  await receiverPc.setLocalDescription();
  await senderPc.setRemoteDescription(receiverPc.localDescription);

  message = await generateKeyFrame(sender.transform.port);
  assert_equals(message.result, 'failure');
  assert_equals(message.value, 'InvalidStateError', `Message: ${message.message}`);

  senderPc.getTransceivers()[0].direction = 'sendonly';
  await senderPc.setLocalDescription();
  await receiverPc.setRemoteDescription(senderPc.localDescription);
  await receiverPc.setLocalDescription();
  await senderPc.setRemoteDescription(receiverPc.localDescription);

  message = await generateKeyFrame(sender.transform.port);
  assert_equals(message.result, 'success');
}, 'generateKeyFrame rejects when the sender is negotiated inactive, and resumes succeeding when negotiated back to active');

promise_test(async (test) => {
  const {sender, receiver, senderPc, receiverPc} = await createConnectionWithTransform(test, 'script-transform-generateKeyFrame.js', {video: true});
  let message = await waitForFrame(sender.transform.port);
  assert_equals(message, 'got frame');

  message = await generateKeyFrame(sender.transform.port);
  assert_equals(message.result, 'success');

  senderPc.getTransceivers()[0].stop();

  message = await generateKeyFrame(sender.transform.port);
  assert_equals(message.result, 'failure');
  assert_equals(message.value, 'InvalidStateError', `Message: ${message.message}`);
}, 'generateKeyFrame rejects when the sender is stopped, even without negotiation');

promise_test(async (test) => {
  const {sender, receiver, senderPc, receiverPc} = await createConnectionWithTransform(test, 'script-transform-generateKeyFrame.js', {video: true});
  let message = await waitForFrame(sender.transform.port);
  assert_equals(message, 'got frame');

  message = await generateKeyFrame(sender.transform.port);
  assert_equals(message.result, 'success');

  await senderPc.getTransceivers()[0].sender.replaceTrack(null);

  message = await generateKeyFrame(sender.transform.port);
  assert_equals(message.result, 'failure');
  assert_equals(message.value, 'InvalidStateError', `Message: ${message.message}`);
}, 'generateKeyFrame rejects with a null track');

// TODO: It would be nice to be able to test that pending generateKeyFrame
// promises are _rejected_ when the transform is unset, or the sender stops
// sending. However, getting the timing on this right is going to be very hard.
// While we could stop the processing of frames before calling
// generateKeyFrame, this would not necessarily help, because generateKeyFrame
// promises are resolved _before_ enqueueing the frame into |readable|, and
// right now the spec does not have a high water mark/backpressure on
// |readable|, so pausing would not necessarily prevent the enqueue.
    </script>
  </body>
</html>

